{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Uniquely Identifying Particles With Hashes\n",
    "\n",
    "In many cases, one can just identify particles by their position in the particle array, e.g. using ``sim.particles[5]``. However, in cases where particles might get reordered in the particle array finding a particle might be difficult. This is why we added a *hash* attribute to particles.\n",
    "\n",
    "In REBOUND particles might get rearranged when a tree code is used for the gravity or collision routine, when particles merge, when a particle leaves the simulation box, or when you manually remove or add particles. In general, therefore, the user should not assume that particles stay at the same index or in the same location in memory.  The reliable way to access particles is to assign them hashes and to access particles through them. Assigning hashes make ``sim.particles`` to behave like Python's `dict` while keeping list-like integer-based indexing at the same time.\n",
    "\n",
    "**Note**: When you don't assign particles a hash, they automatically get set to 0.  The user is responsible for making sure hashes are unique, so if you set up particles without a hash and later set a particle's hash to 0, you don't know which one you'll get back when you access hash 0.  See [Possible Pitfalls](#Possible-Pitfalls) below.\n",
    "\n",
    "In this example, we show the basic usage of the *hash* attribute."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import rebound\n",
    "sim = rebound.Simulation()\n",
    "sim.add(m=1., hash=999)\n",
    "sim.add(a=0.4, hash=\"mercury\")\n",
    "sim.add(a=1., hash=\"earth\")\n",
    "sim.add(a=5., hash=\"jupiter\")\n",
    "sim.add(a=7.)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now not only access the Earth particle with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "but also with"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[\"earth\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can access particles with negative indices like a list.  We can get the last particle with"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=7.0 y=0.0 z=0.0 vx=0.0 vy=0.3779644730092272 vz=0.0>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[-1]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can also set hash after particle is added."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=7.0 y=0.0 z=0.0 vx=0.0 vy=0.3779644730092272 vz=0.0>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[-1].hash = 'pluto'\n",
    "sim.particles['pluto']"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Details"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We usually use strings as hashes, however, under the hood hash is an unsigned integer (`c_uint`). There is a function `rebound.hash` that calculates actual hash of a string."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "c_uint(1424801690)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from rebound import hash as h\n",
    "h(\"earth\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The same function can be applied to integers. In this case it just casts the value to the underlying C datatype (`c_uint`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "c_uint(999)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h(999)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "c_uint(4294967294)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h(-2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we above set the hash to some value, REBOUND converted this value to an unsigned integer using the same `rebound.hash` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "c_uint(999)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[0].hash # particle was created with sim.add(m=1., hash=999)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "c_uint(1424801690)"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[2].hash \n",
    "# particle was created with sim.add(a=1., hash=\"earth\")\n",
    "# so the hash is the same as h(\"earth\") above"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we use string as an index to access particle, function `rebound.hash` is applied to the index and a particle with this hash is returned. On the other hand, if we use integer index, it is not treated as a hash, REBOUND just returns a particle with given position in array, i.e. `sim.particles[0]` is the first particle, etc.\n",
    "\n",
    "We can access particles through their hash directly. However, to differentiate from passing an integer index, we have to first cast the hash to the underlying C datatype by using `rebound.hash` manually."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=1.0 x=0.0 y=0.0 z=0.0 vx=0.0 vy=0.0 vz=0.0>"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[h(999)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "which corresponds to `particles[0]` as it should.  `sim.particles[999]` would try to access index 999, which doesn't exist in the simulation, and REBOUND would raise an AttributeError."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The hash attribute always returns the appropriate unsigned integer ctypes type. (Depending on your computer architecture, `ctypes.c_uint32` can be an alias for another `ctypes` type).\n",
    "\n",
    "So we could also access the earth with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=1.0 y=0.0 z=0.0 vx=0.0 vy=1.0 vz=0.0>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[h(1424801690)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The numeric hashes could be useful in cases where you have a lot of particles you don't want to assign individual names, but you still need to keep track of them individually as they get rearranged:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(1,100):\n",
    "    sim.add(m=0., a=i, hash=i)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "95.0"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[99].a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "98.99999999999999"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.particles[h(99)].a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Possible Pitfalls\n",
    "The user is responsible for making sure the hashes are unique.  If two particles share the same hash, you could get either one when you access them using their hash (in most cases the first hit in the `particles` array).  Two random strings used for hashes have a $\\sim 10^{-9}$ chance of clashing.  The most common case is setting a hash to 0:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=0.0 x=5.0 y=0.0 z=0.0 vx=0.0 vy=0.4472135954999579 vz=0.0>"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim = rebound.Simulation()\n",
    "sim.add(m=1., hash=0)\n",
    "sim.add(a=1., hash=\"earth\")\n",
    "sim.add(a=5.)\n",
    "sim.particles[h(0)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we expected to get back the first particle, but instead got the last one.  This is because we didn't assign a hash to the last particle and it got automatically set to 0.  If we give hashes to all the particles in the simulation, then there's no clash:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rebound.Particle object, m=1.0 x=0.0 y=0.0 z=0.0 vx=0.0 vy=0.0 vz=0.0>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim = rebound.Simulation()\n",
    "sim.add(m=1., hash=0)\n",
    "sim.add(a=1., hash=\"earth\")\n",
    "sim.add(a=5., hash=\"jupiter\")\n",
    "sim.particles[h(0)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Due to details of the `ctypes` library, comparing two `ctypes.c_uint32` instances for equality fails:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h(32) == h(32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You have to compare the value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "h(32).value == h(32).value"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "See the docs for further information: https://docs.python.org/3/library/ctypes.html"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
